Imports Microsoft.SqlServer.Dts.Runtime
Imports System.Net.NetworkInformation
Imports System.Text.RegularExpressions
Imports System.IO
Imports System.Net.Mail
Imports System.Net

' The LogProviderType property is required but not used.
'  The custom log provider will not appear in the list
'  if a LogProviderType is not provided.
<System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable")> _
<System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1724:TypeNamesShouldNotMatchNamespaces")> _
<DtsLogProvider(DisplayName:="Custom log provider for mail messages (VB)", _
Description:="Writes log entries for events to a mail message", _
LogProviderType:="Custom")> _
Public Class EmailLogProviderVB
    Inherits LogProviderBase

#Region " Variables and Constants "

    ' Constants.
    Private Const SUBCOMPONENT As String = "EmailLogProviderVB"
    Private Const PACKAGE_END_EVENT As String = "PackageEnd"

    ' Variables.
    Private _connections As Microsoft.SqlServer.Dts.Runtime.Connections
    Private _events As Microsoft.SqlServer.Dts.Runtime.IDTSInfoEvents
    Private _configString As String
    Private _configArgs As String()
    Private _smtpServer As String
    Private _messageTo As String
    Private _messageFrom As String
    Private _messageFormat As String
    Private _htmlLogWriter As HtmlLogWriterVB
    Private _textLogWriter As TextLogWriterVB
    Private _messageStream As MemoryStream
    Private _messageStreamwriter As StreamWriter

    ' Status flags.
    Private _fireEventsAgain As Boolean
    Private _loggingAlreadyStarted As Boolean
    Private _packageHasEnded As Boolean

#End Region

#Region " Initialize and Validate "

    Public Overrides Sub InitializeLogProvider(ByVal connections As Microsoft.SqlServer.Dts.Runtime.Connections, ByVal events As Microsoft.SqlServer.Dts.Runtime.IDTSInfoEvents, ByVal refTracker As Microsoft.SqlServer.Dts.Runtime.ObjectReferenceTracker)
        ' Cache object references for later use.
        _connections = connections
        _events = events

        _fireEventsAgain = True
    End Sub

    Public Overrides Function Validate(ByVal events As Microsoft.SqlServer.Dts.Runtime.IDTSInfoEvents) As Microsoft.SqlServer.Dts.Runtime.DTSExecResult
        If events Is Nothing Then
            Throw New ArgumentNullException("events")
        End If

        ' This regular expression is just one of many variations that can be found
        '  on the Internet for validating email addresses.
        Const EMAIL_ADDRESS_PATTERN As String = _
          "^[a-zA-Z][\w\.-]*[a-zA-Z0-9]@[a-zA-Z0-9][\w\.-]*[a-zA-Z0-9]\.[a-zA-Z][a-zA-Z\.]*[a-zA-Z]$"

        Dim smtpServerPing As Ping
        Dim smtpServerReply As PingReply
        Dim emailAddressTest As Regex

        _configString = Me.ConfigString

        ' Configuration string should contain 4 arguments.
        ' Validate function needs to validate:
        ' 1. Has a config string been provided?
        ' 2. Does the config string contain 4 values?
        ' 3. Does SMTP server name appear to be valid?
        ' 4. Is To address in correct format?
        ' 5. Is From address in correct format?
        ' 6. Is message format argument "text" or "html"?

        ' Validation test 1: Has a config string been provided?
        If _configString.Length = 0 Then
            events.FireError(0, SUBCOMPONENT, _
              "The log provider did not receive configuration information.", _
              String.Empty, 0)
            Return DTSExecResult.Failure
        End If

        ' Validation test 2: Does the config string contain 4 values?
        _configArgs = _configString.Split(New Char() {";"c})
        If _configArgs.Length <> 4 Then
            events.FireError(0, SUBCOMPONENT, _
              "The log provider configuration does not contain the 4 expected arguments.", _
              String.Empty, 0)
        End If

        ' Extract the 4 values from config string.
        _smtpServer = _configArgs(0)
        _messageTo = _configArgs(1)
        _messageFrom = _configArgs(2)
        _messageFormat = _configArgs(3).ToLower(System.Globalization.CultureInfo.InvariantCulture)

        ' Validation test 3. Does SMTP server name appear to be valid? Ping the server.
        smtpServerPing = New Ping()
        smtpServerReply = smtpServerPing.Send(_smtpServer)
        If smtpServerReply.Status <> IPStatus.Success Then
            events.FireError(0, SUBCOMPONENT, _
              "The specified SMTP server '" & _smtpServer & _
              "' cannot be reached." & smtpServerReply.Status.ToString(), _
              String.Empty, 0)
        End If

        ' Validation test 4: Is To address in correct format?
        emailAddressTest = New Regex(EMAIL_ADDRESS_PATTERN)
        If Not emailAddressTest.IsMatch(_messageTo) Then
            events.FireError(0, SUBCOMPONENT, _
              "The To address '" & _messageTo & _
              "' does not appear to be in the proper format.", _
              String.Empty, 0)
        End If

        ' Validation test 5: Is From address in correct format?
        If Not emailAddressTest.IsMatch(_messageFrom) Then
            events.FireError(0, SUBCOMPONENT, _
              "The To address '" & _messageFrom & _
              "' does not appear to be in the proper format.", _
              String.Empty, 0)
        End If

        ' Validation test 6: Is message format argument "text" or "html"?
        If _messageFormat <> "text" AndAlso _messageFormat <> "html" Then
            events.FireError(0, SUBCOMPONENT, _
              "The message format must be either 'text' or 'html'.", _
              String.Empty, 0)
        End If

        Return DTSExecResult.Success

    End Function

#End Region

#Region " OpenLog and CloseLog "

    Public Overrides Sub OpenLog()

        Dim subComponentInfo As String = _
          SUBCOMPONENT & "-OpenLog (" & _messageFormat & ")"

        If _fireEventsAgain Then
            _events.FireInformation(0, subComponentInfo, "Opening email log.", String.Empty, 0, _fireEventsAgain)
        End If

        If Not _loggingAlreadyStarted Then
            _messageStream = New MemoryStream()
            _messageStreamwriter = New StreamWriter(_messageStream)

            Select Case _messageFormat
                Case "text"
                    _textLogWriter = New TextLogWriterVB()
                    _textLogWriter.InitializeLogWriter(_connections, _events, SUBCOMPONENT, _fireEventsAgain)
                    _textLogWriter.OpenLogText(_messageStreamwriter)

                Case "html"
                    _htmlLogWriter = New HtmlLogWriterVB()
                    _htmlLogWriter.InitializeLogWriter(_connections, _events, SUBCOMPONENT, _fireEventsAgain)
                    _htmlLogWriter.OpenLogHtml(_messageStreamwriter)
            End Select

            _loggingAlreadyStarted = True
        End If
    End Sub

    <System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")> _
    Public Overrides Sub CloseLog()
        Dim messageSubject As String
        Dim isBodyHtml As Boolean
        Dim messageStreamReader As StreamReader
        Dim messageBody As String

        Dim subComponentInfo As String = _
          SUBCOMPONENT & "-CloseLog (" & _messageFormat & ")"

        If _fireEventsAgain Then
            _events.FireInformation(0, subComponentInfo, "Closing email log.", String.Empty, 0, _fireEventsAgain)
        End If

        ' The following IF test is necessary because
        '  OpenLog and CloseLog are called multiple times.
        '  Omitting this test results in multiple mail messages.

        If _packageHasEnded Then
            messageSubject = "SSIS Package Log (" & _messageFormat & ") - " _
                & DateTime.Now.ToString(System.Globalization.CultureInfo.InvariantCulture)

            Select Case _messageFormat
                Case "text"
                    _textLogWriter.CloseLogText()
                    isBodyHtml = False
                Case "html"
                    _htmlLogWriter.CloseLogHtml()
                    isBodyHtml = True
            End Select

            Try
                _messageStreamwriter.Flush()

                _messageStream.Position = 0
                messageStreamReader = New StreamReader(_messageStream)
                messageBody = messageStreamReader.ReadToEnd()

                _messageStreamwriter.Close()
                messageStreamReader.Close()

                Me.SendMailMessage(_messageTo, _messageFrom, _
                    messageSubject, messageBody, _
                    isBodyHtml, _smtpServer)

            Catch ex As Exception
                Console.WriteLine(ex.Message)
                _events.FireError(0, subComponentInfo, ex.Message, String.Empty, 0)
            End Try
        End If
    End Sub

#End Region

#Region " Send Mail Message "

    <System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")> _
    <System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")> _
    Private Sub SendMailMessage( _
        ByVal SendTo As String, ByVal [From] As String, _
        ByVal Subject As String, ByVal Body As String, _
        ByVal IsBodyHtml As Boolean, ByVal Server As String)

        Dim myMessage As MailMessage
        Dim mySmtpClient As SmtpClient

        myMessage = New MailMessage( _
            SendTo, [From], Subject, Body)
        myMessage.IsBodyHtml = IsBodyHtml

        mySmtpClient = New SmtpClient(Server)
        mySmtpClient.Credentials = CredentialCache.DefaultNetworkCredentials
        mySmtpClient.Send(myMessage)
    End Sub

#End Region

#Region " Write individual log entries "

    <System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")> _
    Public Overrides Sub Log(ByVal logEntryName As String, ByVal computerName As String, ByVal operatorName As String, ByVal sourceName As String, ByVal sourceID As String, ByVal executionID As String, ByVal messageText As String, ByVal startTime As Date, ByVal endTime As Date, ByVal dataCode As Integer, ByVal dataBytes() As Byte)

        Dim subComponentInfo As String = _
            SUBCOMPONENT & "-Log (" & _messageFormat & ")"

        If _fireEventsAgain Then
            _events.FireInformation(0, subComponentInfo, "Writing email log entry for " & logEntryName, _
            String.Empty, 0, _fireEventsAgain)
        End If

        Try
            Select Case _messageFormat
                Case "text"
                    _textLogWriter.LogText(logEntryName, computerName, operatorName, _
                      sourceName, sourceID, executionID, _
                      messageText, startTime, endTime, _
                      dataCode, dataBytes)

                Case "html"
                    _htmlLogWriter.LogHtml(logEntryName, computerName, operatorName, _
                      sourceName, sourceID, executionID, _
                      messageText, startTime, endTime, _
                      dataCode, dataBytes)
            End Select

            If logEntryName = PACKAGE_END_EVENT Then
                _packageHasEnded = True
            End If

        Catch ex As Exception
            Console.WriteLine(ex.Message)
            _events.FireError(0, subComponentInfo, ex.Message, String.Empty, 0)
        End Try
    End Sub

#End Region

End Class
